Category Theory for Programmers 1-7
Chapter 1, Category: The Essence of Composition
简单引入了一下范畴,及其在计算机科学中的意义。
范畴是对象及态射的组合。范畴的精髓在于组合。
组合具备两个性质:
结合律:组合的顺序不影响结果。
恒等律:每个对象都有一个恒等态射,组合它不会改变对象。
编程的本质就是分解和组合。受限于人类的心智负担,我们往往将大问题分解成小问题,实现小问题之后,忽略实现的细节,将小问题组合成大问题的解决方案。在这个层面上,范畴论提供了一个抽象的框架来理解这种分解和组合的过程。
Chapter 2, Types and Functions
类型能够检查并拒绝一部分错误的程序。但是类型系统并不能完全保证程序在语义上正确。
类型的最简单和最直观的理解是值的集合。比如 Char 类型的值是所有字符的集合,Int 类型的值是所有整数的集合。接下来,集合对应的范畴称为 Set。Set 范畴中对象是集合,态射则是从一个集合到另一个集合的映射,即函数。但是在 Haskell 中,某些函数可能涉及递归而不能终止,此时 Haskell 函数就无法从一个集合映射到另一个集合。Haskell 的一种处理方法是引入 Bottom,这个 Bottom 是所有类型的成员。可能返回 Bottom 的函数称为部分函数(Partial Function),因此 Haskell 类型和函数构成的范畴称为 Hask 而非 Set。
相比操作语义,指称语义更适合对程序进行形式化证明,因为所有程序行为都被映射到数学对象上。使用范畴可以让我们在程序和数学对象之间建立关系。
在编程语言中,给定相同输入总是产生相同结果、且没有副作用的函数,称为纯函数。在 Haskell 这样的纯函数式语言中,所有函数都是纯的。副作用是指函数在执行过程中对外部环境产生的影响,比如修改全局变量、打印输出、抛出异常等。真实环境中可能无法避免副作用,比如 IO 操作,但是使用一些工具可以将副作用封装起来,使得程序的核心逻辑仍然保持纯函数的特性。
Void 是 Haskell 中对应空集的类型。absurd 是一个函数,它的输入类型是 Void,输出类型是任意类型 a。由于 Void 类型没有值,因此 absurd 函数永远不会被调用,因为根本构造不出任何参数值。如果对 C-H 同构有了解,这个在逻辑上对应 False -> ...,即从假命题可以推出任意命题。
absurd :: Void -> aHaskell 中 () 类型对应单元素集合,() 是它的唯一值。当 () 作为参数类型时,通过传入 () 值来调用函数。当 () 作为返回类型时,函数的返回值总是 ()。这对应 C++ 中的 void 类型。比如:
f44: () -> Int
f44 () = 44
unit :: Int -> ()
unit _ = ()Challenges
def memoize(f):
cache = {}
def memoized(x):
if x not in cache:
cache[x] = f(x)
return cache[x]
return memoized使用 Haskell 写这个 memoize 比较困难。
不能随机化。
记忆化种子是正确的。
(a) 纯的;(b) 不纯;(c) 不纯;(d) 不纯。
从语法上比较 Bool 到 Bool 有无穷多种函数,但是从语义上比较,只有四种函数:恒真、恒假、恒等、取反,恰好对应 4 个态射。
→ Void → () → Bool
Void id unit absurd
() (无) id true, false
Bool (无) unit id, not, yes, noChapter 3, Categories Great and Small
最简单的范畴是一个没有对象或态射的空范畴(这可能在范畴的范畴中很有用)。如果从有向图出发,图中的每个节点都对应一个对象,这个图上所有潜在的边都对应一个态射,就得到了一个范畴(这个从有向图构建的范畴也称为自由范畴)。
预序集、偏序集和全序集都可以被构造成范畴。以预序集为例,预序集中的元素对应范畴中的对象,预序关系对应态射。
在范畴
幺半群(Monoid)是配备了一个二元运算
比如可以在 Haskell 中这样定义幺半群,并给出一个实例:

每个幺半群都可以被描述为一个单对象范畴,每个幺半群的元素都被构造为一个这个单对象到自身的态射,比如单位元素 "abc" 对应态射 (++) "abc",可以发现恰好 mappend 将幺半群集合中的一个元素映射为作用于该集合的一个函数,而态射复合即函数复合 .。
Challenges
略
(a) 偏序集;(b) 感觉得考虑类型的同构,暂且可以看成预序集?
(Bool, &&, True), (Bool, ||, False)
单对象为 Bool,(&& True) 为恒等态射,(&& False) 是另一个态射,把所有 Bool 映射为 False。
单对象为 Z,态射分别为 (+) 0, (+) 1, (+) 2,态射的复合可以列个表,比如 (+) 1 . (+) 2 = (+) 0,满足结合律和单位律。
Chapter 4, Kleisli Categories
前面几个章节的工作都集中在纯函数上,这一章会引入 Monad 来处理副作用。从一个例子出发:
string logger;
bool negate(bool b) { logger += "Not so! "; return !b; }这个 negate 函数有副作用,它会修改外部的 logger 变量。在现代并发编程中,这可能导致数据竞争或者其他不可预测的行为。
一个解决方法是,把 logger 作为函数的参数传入:
pair<string, bool> negate(string logger, bool b) { logger += "Not so! "; return {logger, !b}; }作为 API 这个设计不够好,程序员不得不在调用这个 API 时关注 logger 对象的管理。一个更好的抽象是,将副作用作为函数的返回值的一部分:
pair<string, bool> negate(bool b) { return { "Not so! ", !b }; }在这个例子中,假如我们有另外的一个函数 identity : bool -> bool,它的实现是:
bool identity(bool b) { logger += "So! "; return b; }相似地,这个函数也会被提升为:
pair<string, bool> identity(bool b) { return { "So! ", b }; }这意味着原先的函数复合 identity(negate(...)) 不再正确,因为 negate 的返回值是一个 pair,而 identity 期望的输入是一个 bool。为了让这两个函数可以复合,我们需要一个新的复合,它能从 negate 的返回值中提取出 bool 值,然后传递给 identity,同时把两个 log 信息合并起来。
用 Haskell 更容易表达,定义一个 Writer 类型:
type Writer a = (a, String)态射是 a -> Writer b 的函数,我们用 >=> 来表示这种态射的复合:
(>=>) :: (a -> Writer b) -> (b -> Writer c) -> (a -> Writer c)>=> 的实现是:
f >=> g = \x -> let (y, log1) = f x
(z, log2) = g y
in (z, log1 ++ log2)在这个范畴内的恒等态射是 return 函数,它将一个值提升为 Writer 类型:
return :: a -> Writer a
return x = (x, "")这就是 Kleisli 范畴的一个基本例子。Kleisli 范畴是基于 Monad 的范畴,就目前我们的讨论而言,一个 Kleisli 范畴以底层编程语言的类型作为对象;从类型 A 到类型 B 的态射,是从 A 到某个由 B 经过特定修饰派生出来的类型的函数。每个 Kleisli 范畴都定义了自己的一套复合这类态射的方式,以及相对于该复合的恒等态射。
Challenges
定义一个 Maybe Monad:

2 3. 类似
Chapter 5, Products and Coproducts
范畴论中有一个用于依据关系来定义对象的常见构造,叫做泛构造(universal construction)。做法之一是:先挑出一个由对象和态射搭成的特定形状,然后在范畴中寻找它的所有出现。
初始对象是这样一类对象:对范畴中的任何对象,从初始对象出发到该对象的态射是唯一的。初始对象并不是唯一的,但是在同构意义下是唯一的。
在集合与函数构成的范畴中,初始对象是空集 Void 类型,从 Void 到任意类型的函数对应 absurd 函数。
absurd :: Void -> a类似的(稍后我们会说这个对偶关系),终止对象定义为:对范畴中的任何对象,从该对象出发到终止对象的态射是唯一的。
在集合与函数构成的范畴中,终止对象是单元素集合 () 类型,从任意类型到 () 的函数对应 unit 函数。
unit :: a -> ()
unit _ = ()直观上,定义始对象和终对象的方式之间存在着一种对称性。对任何范畴
态射
积类型也可以在范畴中找到类似的泛构造。我们构造这样的一个模式,一个对象
在集合和函数构成的范畴中,已知两个对象
TBD